import BrowserOnly from '@docusaurus/BrowserOnly'
import CodeBlock from '@theme/CodeBlock'
import { FrameworkTabs } from '../components/framework-tabs'
import { PropsTable } from '@site/src/components/PropsTable'
import { generateDataRecords } from '../utils/data'
import { XYWrapper, XYWrapperWithInput } from '../wrappers'

export const newComponent = (name, props) => ({
  name: name,
  props: { x: d=>d.x, y: d => d.y, ...props},
  key: "components"
})

export const classnames = {
  "StackedBar": "[StackedBar.selectors.bar]",
  "Line": "[Line.selectors.line]",
  "Scatter": "[Scatter.selectors.point]"
}

export const tooltipProps = (chart="StackedBar", components, n=10) => ({
  name: "Tooltip",
  configKey: "tooltip",
  data: generateDataRecords(n).map(d => ({...d, y: Math.floor(d.y)})),
  height: 150,
  components: [newComponent(chart, components)],
  triggers: {
    [classnames[chart]]: d=> `
      <span>${d.x}, ${d.y}</span>
    `
  },
  showTriggers: false,
})

export const Tooltip = (props) => (
  <BrowserOnly fallback={<div>Loading...</div>}>
    {() => {
      const { showTriggers, inputWrapper, triggers, ...rest } = props
      const tsimports = { ['@unovis/ts']: [] }
      const triggerConfig = Object.keys(triggers).reduce((obj,k) => {
        const [component, _, selector] = k.substring(1, k.length-1).split('.')
        if (showTriggers) tsimports['@unovis/ts'].push(component)
        const { [component]: comp } = require('@unovis/ts')
        obj[comp.selectors[selector]] = props.triggers[k]
        if (!props.components.find(d => d.name === component)) {
          props.components.push(newComponent(component))
        }
        return obj
      }, {})
      const wrapperProps = showTriggers ? { ...rest, triggers, imports: tsimports } : rest
      return (
        inputWrapper ?
        <XYWrapperWithInput {...wrapperProps} hiddenProps={{ triggers: triggerConfig, ...props.hiddenProps }}/> :
        <XYWrapper {...wrapperProps} hiddenProps={{ triggers: triggerConfig, ...props.hiddenProps }}/>
      )
    }}
  </BrowserOnly>
)

## Basic Configuration
The _Tooltip_ component allows you to add informative text when hovering over a chart element.
It can work within both XYContainer and SingleContainer, or as a standalone component.

Here is a minimal _Tooltip_ configuration alongside a _StackedBar_ chart:

<Tooltip {...tooltipProps()} showContext="full" showTriggers/>

## Triggers
The `triggers` property allows a _Tooltip_ component to display custom content for a given CSS selector.

It accepts an object of the following type:

```ts
typeof triggers = {
  [selector: string]: (
    datum: T,
    i: number,
    els: Element[]
  ) => string | HTMLElement | null | void
}
```

Where the key _selector_ is the CSS class, and the value is a callback function that returns the content.

#### Selectors
For Unovis components, you can import selectors from `@unovis/ts` and use them as keys in the `triggers` object.

```ts
import { StackedBar } from '@unovis/ts'

const triggers = {
  [StackedBar.selectors.bar]: /* Your callback function*/
}
```

#### Trigger function
The arguments of the callback can be used to customize the string content or custom HTML element:
- `datum`: the data point associated with the hovered element
- `i`: the datum's array index
- `els`: an array of all elements with the selector

:::tip

Returning `null` will hide the tooltip, while returning nothing or `undefined` will
show the tooltip but won't render anything.

You can use this behavior to make _Tooltip_ work with Portals (e.g. `createPortal` in React),
when you want to render your custom tooltip component into Unovis's _Tooltip_.

:::

#### Example: Multiple Triggers
Consider a composite XY chart with a _StackedBar_, _Line_, and _Scatter_ components.
You can define a different trigger function for individual component selectors.

```ts
import { Line, Scatter, StackedBar } from '@unovis/ts'

const triggers = {
  [StackedBar.selectors.bar]: d => `${d.y1}<br/>${d.y2}<br/>${d.y3}`,
  [Scatter.selectors.point]: d => `${(d.y1 + d.y2 + d.y3) / 3}`,
  [Line.selectors.line]: () => 'Average value'
}
```

<Tooltip
  {...tooltipProps()}
  excludeTabs
  components={[
    newComponent('StackedBar', { x: d=>d.x, y: [d => d.y1, d => d.y2, d => d.y4] }),
    newComponent('Line', { x: d=>d.x, y: d => (d.y1 + d.y2 + d.y4) / 3, color: 'var(--vis-color4)'}),
    newComponent('Scatter', { x: d=>d.x, y: d => (d.y1 + d.y2 + d.y4) / 3, color: 'var(--vis-color4)' }),
  ]}
  triggers={{
    "[StackedBar.selectors.bar]": d => `${d.y4}<br/>${d.y2}<br/>${d.y1}`,
    "[Scatter.selectors.point]": (d, i) =>  `Point ${i}: ${(d.y1 + d.y2 + d.y4) / 3}`,
    "[Line.selectors.line]": () => `Average`
  }}
/>

## Position
### Horizontal Placement
<Tooltip {...tooltipProps("Scatter")} horizontalPlacement="left" containerProps={{ padding: { left: 100, right: 100 }}}/>

<Tooltip {...tooltipProps("Scatter")} horizontalPlacement="right" containerProps={{ padding: { left: 100, right: 100 }}}/>

### Vertical Placement
<Tooltip {...tooltipProps("Scatter")}
  verticalPlacement="top"
  containerProps={{ padding: { top: 50, bottom: 50 }}}
  triggers={{
    "[Scatter.selectors.point]": d => `<span>x: ${d.x}, y: ${d.y}</span>`
  }}/>

<Tooltip {...tooltipProps("Scatter")}
  verticalPlacement="bottom"
  containerProps={{ padding: { bottom: 50, top: 50}}}
  triggers={{
    "[Scatter.selectors.point]": d => `<span>x: ${d.x}, y: ${d.y}</span>`
  }}/>

### Horizontal Shift
Shift the tooltip horizontally by setting the `horizontalShift` property.
Works only with `horizontalPlacement` set to `Position.Left` or `Position.Right`.

<Tooltip {...tooltipProps("StackedBar", {barWidth: 20})}
  horizontalPlacement={'right'}
  inputWrapper
  property="horizontalShift"
  inputType="range"
  inputProps={{min: 0, max: 100}}
  defaultValue={50}
  containerProps={{ padding: { right: 100, left: 100 }}}
  triggers={ {"[StackedBar.selectors.bar]": d=> `<span>x: ${d.x}<br/>y: ${d.y}</span>`}}/>

### Vertical Shift
Shift the tooltip vertically by setting the `verticalShift` property.
Works only with `verticalPlacement` set to `Position.Top` or `Position.Bottom`.

<Tooltip {...tooltipProps("Line", {lineWidth: 10})}
  data={generateDataRecords(10).map(d => ({ x: d.x, y: Math.cos(d.x)}))}
  height={250}
  inputWrapper
  property="verticalShift"
  inputType="range"
  triggers= {{
    [classnames["Line"]]: () => 'y: cos(x)'
  }}
  inputProps={{min: 0, max: 100}}
  defaultValue={50}
  containerProps={{ padding: { top: 100 }}}/>

## Follow Cursor
By default, the _Tooltip_ will follow the cursor when hovering over the chart elements.
If you want the tooltip to be anchored to the hovered element, you can set the `followCursor`
property to `false`.

<BrowserOnly fallback={<div>Loading...</div>}>
  {() => <Tooltip {...tooltipProps("StackedBar", {}, 15)} hiddenProps={{ container: document.body }} followCursor={false} height={40} />}
</BrowserOnly>

## Hoverable Content
_Tooltip_'s content will become hoverable when you set the `allowHover` property to `true`.
This will keep the content in view when hovering over the tooltip itself. This can be useful for interacting with
text, links, or other elements in the tooltip. If you want to disable this behavior, you can set the `allowHover`
property to `false`.

Here is an example with a _Line_ chart a clickable link on hover:

<Tooltip {...tooltipProps("Line", { lineWidth: 6 })}
  allowHover={true}
  triggers={{
    "[Line.selectors.line]": () => 'Visit <a href="https://unovis.dev" target="_blank">our website</a>'
  }}/>

## Display Delays
You can control when the tooltip appears and disappears by setting delay timers:

- `showDelay`: Adds a delay in milliseconds before the tooltip appears
- `hideDelay`: Adds a delay in milliseconds before the tooltip disappears

This is useful for preventing tooltip flicker when users quickly move their mouse across multiple elements, or to ensure the tooltip stays visible long enough to be read.

<Tooltip {...tooltipProps("Line", { lineWidth: 6 })} showDelay={500} hideDelay={1000} triggers={{ "[Line.selectors.line]": () => 'This tooltip waits 500ms to appear and 1000ms to disappear' }}/>

## Manual Configuration

### Container
By default the _Tooltip_ will be added to the same DOM element where your chart is located. It will also be constrained
to stay within the dimensions of that element. That's not always convenient (for example when you have a sparkline chart
and you need the tooltip to be displayed above it) so there's a way to set the container element manually by using the
`container` property. In the most cases you might want to set `container` to `document.body`.

<BrowserOnly fallback={<div>Loading...</div>}>
  {() => <Tooltip {...tooltipProps("StackedBar", {}, 50)} container={document.body} height={40} />}
</BrowserOnly>

### Components
Similarly, you can manually define the components a _Tooltip_ interacts with using the `components` property. By default
they will be passed from your chart's container (like _XYContainer_), but if you want to use _Tooltip_ independently
you can do that!
```ts
const tooltip = new Tooltip({
  //highlight-next-line
  components: [components],
  ...tooltipConfig,
})
```

### Custom Class Name
In rare cases you might want to add a custom class name to the _Tooltip_ component. You can do that by using the
`className` property.

### Controls
You can manually define the behavior of your _Tooltip_ with the following methods:

- `hide()`:
   hides the tooltip from view
- `render(content:  string | HTMLElement | null | void)`:
   renders the value of `content` in the tooltip
- `place({x: number, y: number})`:
   anchors the tooltip to the coordinate `(x,y)`
- `placeByElement(element: SVGElement | HTMLElement)`:
   anchors the tooltip to the SVG or HTML element
- `show(content: HTMLElement | string, pos: {x: number, y: number})`:
   a shortcut to `render` and `place`. Shows the value of `content` at the coordinate `(x,y)`

export const ButtonTooltip = () => {
  return (
  <>
    <BrowserOnly fallback={<div>Loading...</div>}>
      {() => {
        const { Tooltip } = require('@unovis/ts')
        const { VisXYContainer, VisLine, VisTooltip } = require('@unovis/react')
        const tooltip = new Tooltip({ container: document.body })
        let toggled = false
        function toggleTooltip(e) {
          if (toggled) {
            tooltip.hide()
          } else {
            tooltip.show("👋 I'm a tooltip", { x: e.clientX, y: e.clientY})
          }
          toggled = !toggled
        }
        return <button onClick={toggleTooltip}>Toggle</button>
      }}
    </BrowserOnly>
  </>
)}

export const tooltipFn = (indent = 1) => {
  const t = ' '.repeat(indent * 2)
  return `\n${t.substring(2)}${['function toggleTooltip() {',
    'if (toggled) {',
    `${t}tooltip.hide()`,
    '} else {',
    `${t}tooltip.show("👋 I\'m a tooltip", { x: 0, y: 0 })`,
    '}',
    'toggled = !toggled',
  ].join(`\n${t}`)}\n${t.substring(2)}}`
}

<FrameworkTabs
  angular={{
    html: '<vis-tooltip #tooltip></vis-tooltip>\n<button (click)="toggleTooltip">Toggle</button>',
    ts: `@ElementRef(\'tooltip\') tooltip\ntoggled = false\n${tooltipFn().replace('function ', '')}`
  }}
  react={[
    'import { VisTooltip, VisTooltipRef } from \'@unovis/react\'',
    '',
    'function Tooltip() {',
    '  const [toggled, setToggled] = React.useState(false)',
    '  const tooltip = React.useRef<VisTooltipRef>(null)',
    `  ${tooltipFn(2).replace('toggled = !toggled', 'setToggled(!toggled)')}`,
    '  return (<>',
    '    <button onClick={toggleTooltip}>Toggle</button>',
    '    <VisTooltip ref={tooltip}/>',
    '  </>)',
    '}'].join('\n')}
  svelte={[
    '<script lang=\'ts\'>',
    '  import { VisTooltip } from \'@unovis/svelte\'\n',
    '  let toggled',
    `  ${tooltipFn(2)}`,
    '</script>',
    '',
    '<button onClick={toggleTooltip}>Toggle</button>',
    '<VisTooltip bind:this={tooltip}/>',
  ].join('\n')}
  typescript={[
    'const tooltip = new Tooltip()',
    'let toggled = false',
      tooltipFn(),
    'document.getElementById("btn").addEventListener(\'click\', toggleTooltip)'
  ].join('\n')}
/>
<ButtonTooltip {...tooltipProps()} toggle/>

## Attributes
The `attributes` property allows you to set custom DOM attributes to _Tooltip_'s _div_ element. It can be useful when
you need to refer to your tooltip by using a CSS selector.

<Tooltip {...tooltipProps("StackedBar", { x: d=> d.x - 0.25, y: [d=>d.y, d=>d.y1]})}
  showContext="minimal"
  excludeGraph
  attributes={{
    'type': 'sample-tooltip',
  }}
/>

## CSS Variables
The _Tooltip_ component supports additional styling via CSS variables that you can define for your visualization container. For example:

```css title="styles.css"
.visualization-container-div {
  --vis-tooltip-background-color: '#3f3f3f';
  --vis-tooltip-text-color: '#fefefe';
}
```
<Tooltip {...tooltipProps()} className="custom-tooltip" excludeTabs/>

<details open>
  <summary open>All supported CSS variables</summary>
  <CodeBlock language="css">{
`--vis-tooltip-background-color: rgba(255, 255, 255, 0.95);
--vis-tooltip-border-color: #e5e9f7;
--vis-tooltip-text-color: #000;
--vis-tooltip-shadow-color: rgba(172, 179, 184, 0.35);
--vis-tooltip-backdrop-filter: none;
--vis-tooltip-padding: 10px 15px;
 
/* Dark Theme */
--vis-dark-tooltip-background-color: rgba(30,30,30, 0.95);
--vis-dark-tooltip-text-color: #e5e9f7;
--vis-dark-tooltip-border-color: var(--vis-color-grey);
--vis-dark-tooltip-shadow-color: rgba(0,0,0, 0.95);`
}</CodeBlock>
</details>

## Component Props
<PropsTable name="VisTooltip"/>
